package fr.emse.tatiana.remotecontrol;

import java.util.*;

import fr.emse.tatiana.*;
import fr.emse.tatiana.replayable.ContinuousReplayable;
import fr.emse.tatiana.replayable.DiscreteReplayable;
import fr.emse.tatiana.replayable.SynchronisationEvent;

public class RemoteControlBackup extends RemoteControl implements ContinuousReplayable {

	private long startTime = 0; 
	private long endTime = 0;
	private Schedule schedule;
	private Set<DiscreteReplayable> replayerList;
	private Set<ContinuousReplayable> continuousList;
	private int speed = 1;
	private boolean speedchanged = false;
	private boolean paused = true;
	private boolean end = false;
	private long currentTime;
	//private RemoteWidget rw;
	private RemoteWidgetSWT rwSWT;
	private boolean timeChanged;
	public boolean ready=false;

	public void start() {
		start(schedule.getFirstTime(),schedule.getLastTime());
	}

	public void start(long starttime,long endtime) {
		this.startTime = starttime;
		this.endTime= endtime;
		super.start();
	}

	public void run() {
		//rw = new RemoteWidget(this);
		rwSWT = new RemoteWidgetSWT(this);
		ready=true;
		while (!end) {
			this.setCurrentTime(startTime);
			ScheduledTraceEvent currentEvent = schedule.next(schedule.getFirst(),this.currentTime);
			while (currentEvent!=null && getCurrentTime()<=endTime) {
				interrupted();
				while (paused) {
					try {
						System.err.println("REMOTE: paused and waiting");
						this.waitFor();
					} catch (InterruptedException e) {
						if (this.timeChanged) {
							currentEvent=schedule.next(schedule.getFirst(),this.currentTime);
							this.timeChanged=false;
						} // otherwise go test for paused
						if (end) return;
					}
				}
				long waittime= currentEvent.getTime()-getCurrentTime();
				long systime = System.currentTimeMillis(); 
				int currentspeed = this.speed;
				try {
					//System.err.println("REMOTE: wait for " + realToAdjusted(waittime, currentspeed));
					this.waitFor(realToAdjusted(waittime, currentspeed));
					this.setCurrentTime(currentEvent.getTime());
					currentEvent.trigger();
					currentEvent=currentEvent.getNext();
				} catch (InterruptedException e) {
					if (this.timeChanged) {
						currentEvent = schedule.next(schedule.getFirst(), this.currentTime);
						this.timeChanged=false;
					} else if (this.isPaused()){
						this.setCurrentTime(this.currentTime+adjustedToReal(System.currentTimeMillis()-systime, currentspeed));
					} else { // speed was changed
						System.err.println("new speed");
						this.setCurrentTime(this.currentTime+adjustedToReal(System.currentTimeMillis()-systime, currentspeed));
					}	
					if (end) return;
				}
			}
			System.err.println("REMOTE: arrived at the end");
			this.pause();
			try {
				interrupted();
				this.waitFor();
			} catch (InterruptedException e) {
				//get on with it, should now be playing
			}
		}
	}	

	public RemoteControlBackup() {
		schedule = new Schedule();
		replayerList = new HashSet<DiscreteReplayable>();
		continuousList = new HashSet<ContinuousReplayable>();
	}

	public void addDiscreteReplayer(DiscreteReplayable replayer) {
		System.err.println("adding replayer " + replayer);
		replayerList.add(replayer);
		List<Long> events=replayer.getEvents();
		Iterator<Long> i = events.iterator();
		while(i.hasNext()) {
			Long k = i.next();
			if (k>0)
				schedule.insertEvent(new TraceEvent(k, replayer));
		}
		this.setStartTime(schedule.getFirstTime());
		this.endTime=schedule.getLastTime();
		System.err.println("got starttime and endtime:" + this.startTime + " " + this.endTime);
	}


	public void addContinuousReplayer(ContinuousReplayable c) {
		continuousList.add(c);
		List<Long> events=c.getEvents();
		Iterator<Long> i = events.iterator();
		while(i.hasNext()) {
			Long k = i.next();
			if (k>=0)
				schedule.insertEvent(new TraceEvent(k, c));
		}
		this.setStartTime(schedule.getFirstTime());
		this.endTime=schedule.getLastTime();
		System.err.println("got starttime and endtime:" + this.startTime + " " + this.endTime);
	}

	public void setWidget(RemoteWidget rw) {
		//this.rw = rw;
		long start = getStartTime();
		long end = getEndTime();
		rw.setBounds(start,end);
		addDiscreteReplayer(rw); // messes up start and endtime restrictions
		setStartTime(start);
		this.endTime=end; // restore start and endtime restrictions if any.
//		rw.setBounds(0,1);
	}

	public long getStartTime() {
		return this.startTime;
	}

	public void setStartTime(long startTime) {
		this.startTime = startTime;
	}

	public long getEndTime() {
		return this.endTime;
	}

	synchronized public void pause() {
		this.paused=true;
		//rw.showPaused();
		rwSWT.showPaused();
		this.wakeUp();
		Iterator<ContinuousReplayable> j = continuousList.iterator();
		ContinuousReplayable c;
		while (j.hasNext()) {
			c=j.next();
			c.pause();
		}
	}

	synchronized public void play() {
		this.paused=false;
		//rw.showPlaying();
		rwSWT.showPlaying();
		this.wakeUp();
		Iterator<ContinuousReplayable> j = continuousList.iterator();
		ContinuousReplayable c;
		while (j.hasNext()) {
			c=j.next();
			c.play();
		}
	}

	synchronized public long getCurrentTime() {
		return currentTime;
	}



	/**
	 * Dyke thinks this is only called internally. It means that goTo is not forwarded
	 * except to RemoteWidget
	 * 
	 * @param currentTime
	 */
	synchronized private void setCurrentTime(long currentTime) {
		this.currentTime = currentTime;
	}

	synchronized public int getSpeed() {
		return speed;
	}

	synchronized public void setSpeed(int speed) {
		if (speed<=0) {
			speed=1;
		}
		boolean oldpaused = this.paused;
		this.paused=true;
		wakeUp();
		this.speed = speed;
		this.paused=oldpaused;
		wakeUp();
		//rw.showSpeed(speed);
		rwSWT.showSpeed(speed);
		Iterator<ContinuousReplayable> j = continuousList.iterator();
		ContinuousReplayable c;
		while (j.hasNext()) {
			c=j.next();
			c.setSpeed(speed);
		}
	}

	private void waitFor(long l) throws InterruptedException {
		if (l<0) {
			l=0;
		}
		sleep(l);
	}

	private void waitFor() throws InterruptedException {
		sleep(1000000000);
	}

	private synchronized void wakeUp() {
		this.interrupt();
	}

	private long realToAdjusted(long waittime, int speed) {
//		return (long)((double) waittime/Math.pow(2, speed));
		return waittime / speed;
	}

	private long adjustedToReal(long waittime, int speed) {
//		return (long)((double) waittime*Math.pow(2, speed));
		return waittime * speed;
	}

	public synchronized boolean isPaused() {
		return paused;
	}

	public void mark(long time) {

	}

	public synchronized void end() {
		Iterator<DiscreteReplayable> i = replayerList.iterator();
		DiscreteReplayable r;
		while (i.hasNext()) {
			r=i.next();
			r.end();

		}
		Iterator<ContinuousReplayable> j = continuousList.iterator();
		ContinuousReplayable c;
		while (j.hasNext()) {
			c=j.next();
			c.end();
		}
		end=true;		
		this.wakeUp();
	}

	public synchronized boolean getEnd() {
		return end;
	}

	public List<Long> getEvents() {
		return new LinkedList<Long>();
	}

	public void goTo(SynchronisationEvent event) {
		if (event.getNow() != null) {
			long time = event.getNow();
			if (time <= 0) {
				System.err.println("improbable time");
				return;
			}
			System.err.println("REMOTE: goto "+time);
			this.currentTime = time;
			this.timeChanged = true;
			this.wakeUp();
		}
		Iterator<DiscreteReplayable> i = replayerList.iterator();
		DiscreteReplayable r;
		while (i.hasNext() && event.isPropagate()) {
			r=i.next();
			r.goTo(event);
		}
		Iterator<ContinuousReplayable> j = continuousList.iterator();
		ContinuousReplayable c;
		while (j.hasNext() && event.isPropagate()) {
			c=j.next();
			c.goTo(event);
		}
		
	}

	public void setWidget(RemoteWidgetSWT rwSWT) {
		this.rwSWT = rwSWT;
		long start = getStartTime();
		long end = getEndTime();
		rwSWT.setBounds(start,end);
		addDiscreteReplayer(rwSWT); // messes up start and endtime restrictions
		setStartTime(start);
		this.endTime=end; // restore start and endtime restrictions if any.
	}
}
